package com.lp.guoguo.util;


import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.JSONWriter;
import com.lp.guoguo.constant.SecurityConstants;
import org.apache.commons.codec.binary.Base64;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.crypto.Cipher;
import java.io.*;
import java.math.BigInteger;
import java.security.*;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;
import java.util.Map;

/**
 * @description: RSA工具类
 * @author: lipeng
 * @create: 2021-06-08 09:30:14
 **/
public class RSAUtil {
    private static final Logger LOG = LoggerFactory.getLogger(RSAUtil.class);
    /** 指定key的大小 */
    private static final int KEY_SIZE = SecurityConstants.Encrypt.RSA_KEY_SIZE;
    private static final String CHAR_ENCODING = "UTF-8";
    private static final String RSA_ALGORITHM = SecurityConstants.Algorithm.RSA;
    private static final String SIGNATURE_ALGORITHM = SecurityConstants.Algorithm.RSA_SIGNATURE_SHA256;
    private static final int MAX_ENCRYPT_BLOCK = KEY_SIZE / 8 - 11; // 动态计算而不是硬编码
    private static final int MAX_DECRYPT_BLOCK = KEY_SIZE / 8;

    // 使用单例模式的Base64实例
    private static final Base64 BASE64 = new Base64();

    // 添加私有构造函数防止实例化
    private RSAUtil() {
        throw new UnsupportedOperationException("Utility class");
    }

    /**
     * 生成密钥对
     *
     * @throws NoSuchAlgorithmException
     */
    public static Map<String, String> generateKeyPair() throws NoSuchAlgorithmException {

        /** RSA算法要求有一个可信任的随机数源 */
        SecureRandom sr = new SecureRandom();
        /** 为RSA算法创建一个KeyPairGenerator对象 */
        KeyPairGenerator kpg = KeyPairGenerator.getInstance(RSA_ALGORITHM);
        /** 利用上面的随机数据源初始化这个KeyPairGenerator对象 */
        kpg.initialize(KEY_SIZE, sr);
        /** 生成密匙对 */
        KeyPair kp = kpg.generateKeyPair();
        /** 得到公钥 */
        Key publicKey = kp.getPublic();
        byte[] publicKeyBytes = publicKey.getEncoded();
        String pub = BASE64.encodeToString(publicKeyBytes);
        /** 得到私钥 */
        Key privateKey = kp.getPrivate();
        byte[] privateKeyBytes = privateKey.getEncoded();
        String pri = BASE64.encodeToString(privateKeyBytes);

        Map<String, String> map = new HashMap<>(10);
        map.put("publicKey", pub);
        map.put("privateKey", pri);
        RSAPublicKey rsp = (RSAPublicKey)kp.getPublic();
        BigInteger bint = rsp.getModulus();
        byte[] b = bint.toByteArray();
        String retValue = new String(BASE64.encode(b));
        map.put("modulus", retValue);
        return map;
    }

    /**
     * 生成密钥对到指定路径
     *
     * @param path
     */
    public static boolean generateKeyPair(String path) {
        FileWriter writer = null;
        try {
            Map<String, String> keyMap = RSAUtil.generateKeyPair();

            String pubkey = keyMap.get("publicKey");
            String priKey = keyMap.get("privateKey");
            StringBuffer pubKeyBuffer = new StringBuffer();
            pubKeyBuffer.append("-----BEGIN PUBLIC KEY-----").append("\r\n").append(pubkey)
                    .append("-----END PUBLIC KEY-----");
            if (LOG.isDebugEnabled()) {
                LOG.debug("===============生成公钥：{}", pubKeyBuffer.toString());
            }
            // 生成公钥证书文件
            File pubKeyFile = new File(path);
            writer = new FileWriter(pubKeyFile);
            writer.write(pubKeyBuffer.toString());
            writer.flush();
            writer.close();
            System.out.println("");
            StringBuffer privKeyBuffer = new StringBuffer();
            privKeyBuffer.append("-----BEGIN PRIVATE KEY-----").append("\r\n").append(priKey)
                    .append("-----END PRIVATE KEY-----");
            if (LOG.isDebugEnabled()) {
                LOG.debug("===============生成私钥：{}", privKeyBuffer.toString());
            }
            // 生成私钥证书文件
            File privKeyFile = new File(path);
            writer = new FileWriter(privKeyFile);
            writer.write(privKeyBuffer.toString());
            writer.flush();
            writer.close();
            if (LOG.isDebugEnabled()) {
                LOG.debug("===============生成秘钥对成功");
            }
            return true;
        } catch (Exception e) {
            LOG.error("生成秘钥对失败", e);
            return false;
        } finally {
            try {
                if (writer != null) {
                    writer.close();
                }
            } catch (IOException e) {
                LOG.error("", e);
            }
        }
    }

    //公钥加密
    public static String encryptSplit(String source, String publicKeyPath) {
        try {
//            Key key = getPublicKey(new FileInputStream(publicKeyPath));
            RSAPublicKey publicKey = getPublicKey(publicKeyPath);
            /** 得到Cipher对象来实现对源数据的RSA加密 */
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
            cipher.init(Cipher.ENCRYPT_MODE, publicKey);
            //分段加密
            byte[] b = source.getBytes(CHAR_ENCODING);
            /** 执行加密操作 */
            int length = b.length;
            int offset = 0;
//            StringBuffer sb = new StringBuffer();
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            while (length - offset > 0) {
                int size = Math.min(MAX_ENCRYPT_BLOCK, length - offset);
                byte[] encrypted = cipher.doFinal(b, offset, size);
                offset += size;
                out.write(encrypted, 0, encrypted.length);
                // 这里可以将encrypted作为一个分段的密文进行存储或传输
//                encrypted
//                sb.append(base64.encodeToString());
            }
            byte[] encryptedData = out.toByteArray();
            out.close();
//            byte[] b1 = cipher.doFinal(b);
            return BASE64.encodeToString(encryptedData);
        } catch (Exception e) {
            LOG.error("加密异常，请检查RSA公钥", e);
            throw new SecurityException("加密异常", e);
        }
    }


    /**
     * 解密算法 cryptograph:密文
     *
     */
    public static String decryptSplit(String cryptograph, String privateKeyPath) {
        try {
//            Key key = getPrivateKey(new FileInputStream(privateKeyPath));
            RSAPrivateKey privateKey = getPrivateKey(privateKeyPath);
            /** 得到Cipher对象对已用公钥加密的数据进行RSA解密 */
            Cipher cipher = Cipher.getInstance(RSA_ALGORITHM);
            cipher.init(Cipher.DECRYPT_MODE, privateKey);

            byte[] encryptedData = BASE64.decode(cryptograph);
            /** 执行解密操作 */

            int inputLen = encryptedData.length;
            ByteArrayOutputStream out = new ByteArrayOutputStream();
            int offSet = 0;
            byte[] cache;
            int i = 0;
            // 对数据分段解密
            while (inputLen - offSet > 0) {
                if (inputLen - offSet > MAX_DECRYPT_BLOCK) {
                    cache = cipher.doFinal(encryptedData, offSet, MAX_DECRYPT_BLOCK);
                } else {
                    cache = cipher.doFinal(encryptedData, offSet, inputLen - offSet);
                }
                out.write(cache, 0, cache.length);
                i++;
                offSet = i * MAX_DECRYPT_BLOCK;
            }
            byte[] decryptedData = out.toByteArray();
            out.close();



//            byte[] b = cipher.doFinal(b1);
            return new String(decryptedData);
        } catch (Exception e) {
            throw new SecurityException("解密失败", e);
        }
    }

    /**
     * 得到公钥
     *
     * @param key
     *            密钥字符串（经过base64编码）
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     * @throws Exception
     */
    public static RSAPublicKey getPublicKey(String key) throws NoSuchAlgorithmException, InvalidKeySpecException {
        X509EncodedKeySpec keySpec = new X509EncodedKeySpec(BASE64.decode(key));
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
        RSAPublicKey publicKey = (RSAPublicKey)keyFactory.generatePublic(keySpec);
        return publicKey;
    }

    /**
     * 得到私钥
     *
     * @param key
     *            密钥字符串（经过base64编码）
     * @throws NoSuchAlgorithmException
     * @throws InvalidKeySpecException
     * @throws Exception
     */
    public static RSAPrivateKey getPrivateKey(String key) throws NoSuchAlgorithmException, InvalidKeySpecException {
        PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(BASE64.decode(key));
        KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
        RSAPrivateKey privateKey = (RSAPrivateKey)keyFactory.generatePrivate(keySpec);
        return privateKey;
    }

    /**
     * 从文件中加载私钥
     *
     * @param in
     *            私钥文件流
     * @return 是否成功
     * @throws IOException
     * @throws InvalidKeySpecException
     * @throws NoSuchAlgorithmException
     * @throws Exception
     */
    public static RSAPrivateKey getPrivateKey(InputStream in)
            throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(in));
            String readLine = null;
            StringBuilder sb = new StringBuilder();
            while ((readLine = br.readLine()) != null) {
                if (readLine.charAt(0) == '-') {
                    continue;
                } else {
                    sb.append(readLine);
                    sb.append('\r');
                }
            }
            br.close();
            return getPrivateKey(sb.toString());
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException e) {
                LOG.error("", e);
            }
        }
    }

    /**
     * 从文件中输入流中加载公钥
     *
     * @param in
     *            公钥输入流
     * @throws IOException
     * @throws InvalidKeySpecException
     * @throws NoSuchAlgorithmException
     * @throws Exception
     *             加载公钥时产生的异常
     */
    public static RSAPublicKey getPublicKey(InputStream in)
            throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
        try {
            BufferedReader br = new BufferedReader(new InputStreamReader(in));
            StringBuilder sb = new StringBuilder();
            String readLine;
            boolean hasContent = false;

            while ((readLine = br.readLine()) != null) {
                hasContent = true;
                if (!readLine.startsWith("-")) {
                    sb.append(readLine);
                    sb.append('\r');
                }
            }

            if (!hasContent) {
                throw new IOException("Public key file is empty");
            }

            br.close();
            String keyContent = sb.toString().trim();
            if (keyContent.isEmpty()) {
                throw new IOException("No valid key content found in file");
            }

            return getPublicKey(keyContent);
        } finally {
            try {
                if (in != null) {
                    in.close();
                }
            } catch (IOException e) {
                LOG.error("Error closing input stream", e);
            }
        }
    }

    /**
     * 签名
     *
     * @param content
     * @param privateKeyPath
     * @return
     */
    public static String sign(String content, String privateKeyPath) {
        try {
//            PrivateKey priKey = getPrivateKey(new FileInputStream(privateKeyPath));
            RSAPrivateKey priKey = getPrivateKey(privateKeyPath);
            Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
            signature.initSign(priKey);
            signature.update(content.getBytes(CHAR_ENCODING));

            byte[] signed = signature.sign();
            return BASE64.encodeToString(signed);
        } catch (Exception e) {
            throw new SecurityException("签名失败", e);
        }
    }

    /**
     * 验签
     *
     * @param content
     * @param sign
     * @return
     */
    public static boolean checkSign(String content, String sign, String publicKeyPath) {
        try {
//            PublicKey pubKey = getPublicKey(Files.newInputStream(Paths.get(publicKeyPath)));
            RSAPublicKey publicKey = getPublicKey(publicKeyPath);
            Signature signature = Signature.getInstance(SIGNATURE_ALGORITHM);
            signature.initVerify(publicKey);
            signature.update(content.getBytes(CHAR_ENCODING));
            boolean bverify = signature.verify(BASE64.decode(sign));
            return bverify;
        } catch (InvalidKeyException e) {
            LOG.error("公钥证书有误");
        } catch (InvalidKeySpecException e) {
            LOG.error("公钥证书有误");
        } catch (SignatureException e) {
            LOG.error("签名有误");
        } catch (Exception e) {
            LOG.error("其他错误");
        }
        return false;
    }
    /**
     * 保存密钥对到文件
     * @param keyPair 密钥对
     * @param publicKeyPath 公钥保存路径
     * @param privateKeyPath 私钥保存路径
     * @return 是否保存成功
     */
    public static boolean saveKeyPairToFile(Map<String, String> keyPair, String publicKeyPath, String privateKeyPath) {
        try (FileWriter pubWriter = new FileWriter(publicKeyPath);
             FileWriter priWriter = new FileWriter(privateKeyPath)) {

            String publicKey = keyPair.get("publicKey");
            String privateKey = keyPair.get("privateKey");

            // 写入公钥
            pubWriter.write("-----BEGIN PUBLIC KEY-----\n");
            pubWriter.write(publicKey);
            pubWriter.write("-----END PUBLIC KEY-----");

            // 写入私钥
            priWriter.write("-----BEGIN PRIVATE KEY-----\n");
            priWriter.write(privateKey);
            priWriter.write("-----END PRIVATE KEY-----");

            return true;
        } catch (IOException e) {
            LOG.error("保存密钥对失败", e);
            return false;
        }
    }



    public static void main(String[] args) throws NoSuchAlgorithmException, InvalidKeySpecException, IOException {
        saveKeyPairToFile(generateKeyPair(), "F:\\webapps\\lp_chat_im\\src\\main\\resources\\certs\\publicApp.key", "F:\\webapps\\lp_chat_im\\src\\main\\resources\\certs\\privateApp.key");

//        InputStream publicKeyStream = RSAUtil.class.getClassLoader().getResourceAsStream("certs/public.key");
//        InputStream privateKeyStream = RSAUtil.class.getClassLoader().getResourceAsStream("certs/private.key");
//        RSAPublicKey publicKey = getPublicKey(publicKeyStream);
//        RSAPrivateKey privateKey = getPrivateKey(privateKeyStream);
//
////        System.out.println("publicKey = " + publicKey);
////        System.out.println("privateKey = " + privateKey);
//
//        String encrypt = encrypt("123456", "F:\\webapps\\lp_chat_im\\src\\main\\resources\\certs\\public.key");
//        String decrypt = decrypt(encrypt, "F:\\webapps\\lp_chat_im\\src\\main\\resources\\certs\\private.key");
//        System.out.println("encrypt = " + encrypt);
//        System.out.println("decrypt = " + decrypt);

        //
//        secret   time   token
//        time + secret

////  time + 签名  + secret
        //secret  = time + secret + token  + date 数据排序按照unicode码进行排序

        HashMap map = new HashMap();
        map.put("password","password");
        map.put("username","demo");
        map.put("ab3sent","demo");
        map.put("ab2sent","demo");
        map.put("ab21sent","demo");
        map.put("ab1sent","demo");
        map.put("zbsent","demo");



//        String jsonString = JSONObject.toJSONString(users);

//        String jsonString2 = JSONObject.toJSONString(map);
        String jsonString2 = JSONObject.toJSONString(map, JSONWriter.Feature.SortMapEntriesByKeys);
        System.out.println("jsonString2 = " + jsonString2);
        RSAPrivateKey privateAppKey = getPrivateKey(new FileInputStream("D:\\webapp\\lp_chat_im\\src\\main\\resources\\certs\\privateApp.key"));
        RSAPublicKey publicAppKey = getPublicKey(new FileInputStream("D:\\webapp\\lp_chat_im\\src\\main\\resources\\certs\\publicApp.key"));

//        EQjRsM9f56ue7dg7Zdf7gXWi
        String privateKeyStr = BASE64.encodeToString(privateAppKey.getEncoded());
        String publicKeyStr = BASE64.encodeToString(publicAppKey.getEncoded());
        System.out.println("privateKeyStr = " + privateKeyStr);
        System.out.println("publicKeyStr = " + publicKeyStr);

        System.out.println("s = " + privateKeyStr);



        Long time = System.currentTimeMillis();
        System.out.println("time = " + time);

        String encrypt = encryptSplit("你好，Apifox！", publicKeyStr);
        System.out.println("encrypt = " + encrypt);

        String s = decryptSplit("WgAHW2lkET5ZerZbjRZgzcp2vCZ+NXpomz6yu9hJqaamx7FuLCVD6iXGTbQ1q6my/XfUKozvw2cSymyx6eDDp7EuTg63MnhgFNImdP58opSyLVPLgbvcNMaObfPJqXeE8G7IhwjVYtkn97a7Yl1kxkWvVhVEt+Qe3TY5z+tlvzjG+okaRALLmWiGVw+0WNluMzTaycLji9fUmaG3maps4rigSPVhfhITJwYO8FG79jmSMc0QTajW4hPNehx1dnE5IGN91lwtTtt2lnvVupVOPvks9SeIroqy8nuchJCZTW6wX2k6hDqSyZ142vtUH7pttJ9TgmBUUQ8uD9g9+urejA==", privateKeyStr);
        System.out.println(s);


    }
}
